/*日志落地模块（工厂模式）
    落地方向：1.标准输出 2.指定文件 3.滚动文件(以文件大小滚动)
    使用工厂进行创建与表示分离
    支持用户新添别的落地方式
*/
#pragma once
#include "util.hpp"
#include "message.hpp"
#include <memory>
#include <fstream>
#include <cassert>
#include <sstream>

namespace wu {
    class LogSink {
    public:
        using ptr = std::shared_ptr<LogSink>;
        ~LogSink() {}
        virtual void log(const char* data, size_t len) = 0;
    };

    class StdoutSink : public LogSink {
    public:
        void log(const char* data, size_t len) override {
            std::cout.write(data, len);
        }
    };

    class FileSink : public LogSink {
    public:
        FileSink(const std::string &pathname)
            :_pathname(pathname)
        {
            //1.先创建目录
            util::File::createDirectory(util::File::path(_pathname));
            //2.打开文件
            _ofs.open(_pathname, std::ios::binary | std::ios::app);
            assert(_ofs.is_open());//打开失败
        }
        void log(const char* data, size_t len) override {
            _ofs.write(data, len);
            _ofs.flush();//频繁的调用flush会影响性能，待优化
            assert(_ofs.good());
        }
    private:
        std::string _pathname;
        std::ofstream _ofs;
    };

    class RollBySizeSink : public LogSink {
    public:
        RollBySizeSink(const std::string &name, size_t maxfsize)
            :_basename(name), _curfsize(0), _maxfsize(maxfsize), _count(0)
        {
            //1.先创建目录
            std::string filename = createFilename();
            util::File::createDirectory(util::File::path(filename));
            //2.打开文件
            _ofs.open(filename, std::ios::binary | std::ios::app);
            assert(_ofs.is_open());//打开失败
        }
        
        void log(const char* data, size_t len) {
            //写入之前需要先判断当前文件大小和文件的最大大小的关系，如果超过了文件的最大大小的话就需要新建文件再写入
            if(_curfsize >= _maxfsize) {
                //先关闭之前的文件
                _ofs.close();
                //建立新文件
                std::string filename = createFilename();
                util::File::createDirectory(util::File::path(filename));
                _ofs.open(filename, std::ios::binary | std::ios::app);
                assert(_ofs.is_open());//打开失败
                _curfsize = 0;//更新当前文件大小
            }
            _ofs.write(data, len);
            _ofs.flush();//频繁的调用flush会影响性能，待优化
            assert(_ofs.good());
            _curfsize += len;
        }
    private:
        std::string createFilename() {
            //先获取当前时间
            time_t t = time(nullptr);
            struct tm lt;
            localtime_r(&t, &lt);
            std::stringstream ss;
            //构建文件名
            ss << _basename;
            ss << lt.tm_year+1900;
            ss << lt.tm_mon + 1;
            ss << lt.tm_mday;
            ss << lt.tm_hour;
            ss << lt.tm_min;
            ss << lt.tm_sec;
            ss << ".log";//文件后缀
            ss << _count++;
            return ss.str();
        }
    private:
        std::string _basename;//真正文件名=基础文件名+当前时间生成
        std::ofstream _ofs;
        size_t _curfsize;//当前文件大小
        size_t _maxfsize;//文件的最大大小

        int _count;
    };

    //落地工厂
    class FactorySink {
    public:
        //选择落地方式+参数包的形式方便用户之后添加新的落地方式的扩展
        template<class SinkType, class ...Args>
        static LogSink::ptr create(Args &&...args) {
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };
}
